using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace ContractDocumentation
{
    enum ConnectionPosition { Left, Right };

    static class UIUtils
    {
        internal static IEnumerable<FrameworkElement> LayoutChildren(Context context, FrameworkElement parent, IEnumerable<FrameworkElement> children)
        {
            if (children != null && children.Count() == 0) children = null;

            if (parent == null && children == null) return new FrameworkElement[0];
            else if (parent == null) return children;
            else if (children == null) return new FrameworkElement[] { parent };

            Canvas canvas = new Canvas();

            double childrenHeight = 0;
            double firstChildConnectionY = Double.NaN;
            double lastChildConnectionY = Double.NaN;
            foreach (var child in children)
            {
                canvas.Children.Add(child);
                child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                if (Double.IsNaN(firstChildConnectionY)) firstChildConnectionY = GetLeftConnectionPoint(child).Y;
                lastChildConnectionY = GetLeftConnectionPoint(child).Y + childrenHeight;
                childrenHeight += child.DesiredSize.Height + Visualizer.VerticalGap;
            }
            childrenHeight -= Visualizer.VerticalGap;

            canvas.Children.Add(parent);
            parent.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

            double childrenX = parent.DesiredSize.Width + Visualizer.HorizontalGap;
            if (children.Count() > 1) childrenX += Visualizer.HorizontalGap;
            double middleY = (firstChildConnectionY + lastChildConnectionY) / 2;
            Point parentConnectionPoint = GetRightConnectionPoint(parent);
            double parentY = Math.Max(0, middleY - parentConnectionPoint.Y);
            double childrenY = Math.Max(0, parentConnectionPoint.Y - middleY);
            ArrangeInCanvas(parent, new Rect(new Point(0, parentY), parent.DesiredSize));
            parentConnectionPoint.Offset(0, parentY);

            List<SchemaLink> links = new List<SchemaLink>();
            canvas.Children.Add(CreateConnectingLine(parentConnectionPoint.X, parentConnectionPoint.Y, (parent.DesiredSize.Width + childrenX) / 2, parentConnectionPoint.Y));
            canvas.Children.Add(CreateConnectingLine((parent.DesiredSize.Width + childrenX) / 2, firstChildConnectionY, (parent.DesiredSize.Width + childrenX) / 2, lastChildConnectionY));
            foreach (var child in children)
            {
                ArrangeInCanvas(child, new Rect(new Point(childrenX, childrenY), child.DesiredSize));
                Point connectionPoint = GetLeftConnectionPoint(child);
                connectionPoint.Offset(childrenX, childrenY);
                canvas.Children.Add(CreateConnectingLine((parent.DesiredSize.Width + childrenX) / 2, connectionPoint.Y, connectionPoint.X, connectionPoint.Y));
                VisualizerTag tag = (VisualizerTag)child.Tag;
                if (tag != null && tag.Links != null)
                {
                    links.AddRange(LinksOffset(tag.Links, new Vector(childrenX, childrenY)));
                }
                childrenY += child.DesiredSize.Height + Visualizer.VerticalGap;
            }

            canvas.AdjustSize();
            canvas.Tag = new VisualizerTag()
            {
                GetLeftConnectionPoint = delegate
                {
                    Point point = GetLeftConnectionPoint(parent);
                    point.Offset(0, parentY);
                    return point;
                },
                Links = links,
            };
            return new FrameworkElement[] { canvas };
        }

        internal static Rectangle CreateElementOrAttributeRectangle(bool dashed)
        {
            Rectangle rectangle = new Rectangle()
            {
                RadiusX = 3,
                RadiusY = 3,
                Stroke = Brushes.Black,
                StrokeThickness = 1,
                StrokeLineJoin = PenLineJoin.Round,
                StrokeStartLineCap = PenLineCap.Round,
                StrokeEndLineCap = PenLineCap.Round,
            };
            if (dashed)
            {
                double a = rectangle.RadiusX;
                double b = rectangle.RadiusY;
                double ellipseCircumference = Math.PI * (3 * (a + b) - Math.Sqrt((3 * a + b) * (a + 3 * b)));
                rectangle.StrokeDashArray = new DoubleCollection(new double[] { ellipseCircumference / 4 });
                rectangle.StrokeDashOffset = 0;
                rectangle.StrokeDashCap = PenLineCap.Round;
            }
            return rectangle;
        }

        internal static Ellipse CreateGroupEllipse(bool dashed)
        {
            Ellipse ellipse = new Ellipse()
            {
                Fill = Brushes.White,
                Stroke = Brushes.Black,
                StrokeThickness = 1,
                StrokeLineJoin = PenLineJoin.Round,
                StrokeStartLineCap = PenLineCap.Round,
                StrokeEndLineCap = PenLineCap.Round,
            };
            if (dashed)
            {
                ellipse.StrokeDashArray = new DoubleCollection(new double[] { 6 });
                ellipse.StrokeDashOffset = 3;
                ellipse.StrokeDashCap = PenLineCap.Round;
            }
            return ellipse;
        }

        internal static Point GetLeftConnectionPoint(FrameworkElement frameworkElement)
        {
            VisualizerTag visualizerTag = (VisualizerTag)frameworkElement.Tag;
            if (visualizerTag != null && visualizerTag.GetLeftConnectionPoint != null)
            {
                return visualizerTag.GetLeftConnectionPoint();
            }
            else
            {
                return new Point(0, frameworkElement.DesiredSize.Height / 2);
            }
        }

        internal static Point GetRightConnectionPoint(FrameworkElement frameworkElement)
        {
            VisualizerTag visualizerTag = (VisualizerTag)frameworkElement.Tag;
            if (visualizerTag != null && visualizerTag.GetRightConnectionPoint != null)
            {
                return visualizerTag.GetRightConnectionPoint();
            }
            else
            {
                return new Point(frameworkElement.DesiredSize.Width, frameworkElement.DesiredSize.Height / 2);
            }
        }

        internal static FrameworkElement AppendDocumentation(Context context, FrameworkElement frameworkElement,
            string documentation, double maxWidth)
        {
            if (documentation != null && documentation.Length > 0)
            {
                StackPanel stackPanel = new StackPanel();
                StackPanel horizontalStackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
                stackPanel.Children.Add(horizontalStackPanel);
                horizontalStackPanel.Children.Add(frameworkElement);
                frameworkElement.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                TextBlock documentationTextBlock = new TextBlock()
                {
                    Text = documentation,
                    Foreground = Brushes.Gray,
                    Padding = new Thickness(8, 4, 8, 4),
                    MaxWidth = Math.Max(frameworkElement.DesiredSize.Width, 192),
                    TextWrapping = TextWrapping.Wrap,
                };
                stackPanel.Children.Add(documentationTextBlock);

                stackPanel.Tag = new VisualizerTag()
                {
                    GetLeftConnectionPoint = () => UIUtils.GetLeftConnectionPoint(frameworkElement),
                    GetRightConnectionPoint = () => UIUtils.GetRightConnectionPoint(frameworkElement),
                };
                return stackPanel;
            }
            return frameworkElement;
        }

        internal static IEnumerable<SchemaLink> LinksOffset(IEnumerable<SchemaLink> links, Vector offset)
        {
            List<SchemaLink> result = new List<SchemaLink>();
            foreach (SchemaLink link in links)
            {
                result.Add(new SchemaLink()
                {
                    Rect = new Rect(new Point(link.Rect.X + offset.X, link.Rect.Y + offset.Y), link.Rect.Size),
                    Id = link.Id,
                });
            }
            return result;
        }

        static Line CreateConnectingLine(double x1, double y1, double x2, double y2)
        {
            return new Line()
            {
                X1 = x1,
                Y1 = y1,
                X2 = x2,
                Y2 = y2,
                Stroke = Brushes.Black,
                StrokeThickness = 1,
                StrokeLineJoin = PenLineJoin.Round,
                StrokeStartLineCap = PenLineCap.Round,
                StrokeEndLineCap = PenLineCap.Round,
            };
        }

        static void ArrangeInCanvas(UIElement uiElement, Rect rect)
        {
            Canvas.SetLeft(uiElement, rect.Left);
            Canvas.SetTop(uiElement, rect.Top);
            Canvas.SetRight(uiElement, rect.Right);
            Canvas.SetBottom(uiElement, rect.Bottom);
        }

        static Rect GetLocationInCanvas(UIElement uiElement)
        {
            Point point1 = new Point(Canvas.GetLeft(uiElement), Canvas.GetTop(uiElement));
            Point point2 = new Point(Canvas.GetRight(uiElement), Canvas.GetBottom(uiElement));
            return new Rect(point1, point2);
        }

        static void AdjustSize(this Canvas canvas)
        {
            double width = 0;
            double height = 0;
            foreach (UIElement child in canvas.Children)
            {
                Rect rect = GetLocationInCanvas(child);
                width = double.IsNaN(rect.Right) ? width : Math.Max(width, rect.Right);
                height = double.IsNaN(rect.Bottom) ? height : Math.Max(height, rect.Bottom);
            }
            canvas.Width = width;
            canvas.Height = height;
        }
    }
}
